"""
This code implements the Universal Adaptive Normalization Scale (Adaptive Multi-Interval Scale, AMIS).
AMIS is designed for transforming and normalizing heterogeneous data based on adaptive partitioning 
of the measurement range into multiple intervals using statistical characteristics of the sample.
This enables more accurate and flexible scaling compared to conventional linear normalization.
"""

import pandas as pd
import numpy as np
from scipy.interpolate import interp1d
from tkinter import Tk, simpledialog, messagebox, filedialog
import matplotlib.pyplot as plt
import os

def amis_conversion(data, fixed_min=None, fixed_max=None):
    data_min = fixed_min if fixed_min is not None else np.min(data)  # minimum value (data_min)
    data_max = fixed_max if fixed_max is not None else np.max(data)  # maximum value (data_max)
    x5 = np.mean(data)  # mean value (center) (x_5)

    # calculation of mean values in data subintervals (x2, x3, x4, x6, x7, x8)
    x3 = np.nanmean(data[(data >= data_min) & (data <= x5)])
    x7 = np.nanmean(data[(data >= x5) & (data <= data_max)])
    x2 = np.nanmean(data[(data_min <= data) & (data <= x3)])
    x4 = np.nanmean(data[(x3 <= data) & (data <= x5)])
    x6 = np.nanmean(data[(x5 <= data) & (data <= x7)])
    x8 = np.nanmean(data[(x7 <= data) & (data <= data_max)])

    # calculation of mean values in smaller subintervals (for 17-point model)
    x25 = np.nanmean(data[(data_min <= data) & (data <= x2)])
    x35 = np.nanmean(data[(x2 <= data) & (data <= x3)])
    x45 = np.nanmean(data[(x3 <= data) & (data <= x4)])
    x55 = np.nanmean(data[(x4 <= data) & (data <= x5)])
    x65 = np.nanmean(data[(x5 <= data) & (data <= x6)])
    x75 = np.nanmean(data[(x6 <= data) & (data <= x7)])
    x85 = np.nanmean(data[(x7 <= data) & (data <= x8)])
    x95 = np.nanmean(data[(x8 <= data) & (data <= data_max)])

    # formation of node arrays and values for 17-point model (17 nodes)
    x_17 = [data_min, x25, x2, x35, x3, x45, x4, x55, x5,
            x65, x6, x75, x7, x85, x8, x95, data_max]
    y_17 = np.linspace(0, 100, len(x_17))

    # nodes and values for 9-point model
    x_9 = [data_min, x2, x3, x4, x5, x6, x7, x8, data_max]
    y_9 = [0, 12.5, 25, 37.5, 50, 62.5, 75, 87.5, 100]

    # nodes and values for 5-point model
    x_5 = [data_min, x3, x5, x7, data_max]
    y_5 = [0, 25, 50, 75, 100]

    # nodes and values for 3-point model
    x_3 = [data_min, x5, data_max]
    y_3 = [0, 50, 100]

    # nodes and values for linear normalization
    x_line = [data_min, data_max]
    y_line = [0, 100]

    # creation of linear interpolation functions for each model
    interp_17 = interp1d(x_17, y_17, kind='linear', fill_value='extrapolate')
    interp_9 = interp1d(x_9, y_9, kind='linear', fill_value='extrapolate')
    interp_5 = interp1d(x_5, y_5, kind='linear', fill_value='extrapolate')
    interp_3 = interp1d(x_3, y_3, kind='linear', fill_value='extrapolate')
    interp_line = interp1d(x_line, y_line, kind='linear', fill_value='extrapolate')

    # transformation of source data using interpolation functions
    converted_17 = interp_17(data)
    converted_9 = interp_9(data)
    converted_5 = interp_5(data)
    converted_3 = interp_3(data)
    converted_line = interp_line(data)

    return {
        '17_points': converted_17,
        '9_points': converted_9,
        '5_points': converted_5,
        '3_points': converted_3,
        'linear': converted_line
    }, {
        'x_17': x_17, 'y_17': y_17,
        'x_9': x_9, 'y_9': y_9,
        'x_5': x_5, 'y_5': y_5,
        'x_3': x_3, 'y_3': y_3,
        'x_line': x_line, 'y_line': y_line
    }

def read_data_from_file():
    root = Tk()
    root.attributes('-topmost', True)
    root.withdraw()
    file_path = filedialog.askopenfilename(
        title="Select Excel or CSV data file",
        filetypes=[("Excel files", "*.xlsx *.xls"), ("CSV files", "*.csv")],
        initialdir="."
    )
    root.destroy()
    if not file_path:
        return None, None
    if file_path.lower().endswith('.csv'):
        df = pd.read_csv(file_path)
    else:
        df = pd.read_excel(file_path)
    return df, file_path

def select_normalization_bounds():
    root = Tk()
    root.attributes('-topmost', True)
    root.withdraw()
    answer = messagebox.askquestion(
        "Normalization Range",
        "Use fixed boundaries?\n\nYes - manually\nNo - automatically from data",
        parent=root
    )
    min_val, max_val = None, None
    if answer == "yes":
        min_val = simpledialog.askfloat("Minimum value", "Enter minimum scale value:", parent=root)
        max_val = simpledialog.askfloat("Maximum value", "Enter maximum scale value:", parent=root)
    root.destroy()
    return min_val, max_val

def normalize_data(data, min_val, max_val):
    return 100 * (data - min_val) / (max_val - min_val)

def plot_conversion_models(data, points_coords):
    plt.figure(figsize=(10, 6))
    x_min, x_max = np.min(points_coords['x_17']), np.max(points_coords['x_17'])
    x_vals = np.linspace(x_min, x_max, 300)

    interp_17 = interp1d(points_coords['x_17'], points_coords['y_17'], kind='linear', fill_value='extrapolate')
    interp_9 = interp1d(points_coords['x_9'], points_coords['y_9'], kind='linear', fill_value='extrapolate')
    interp_5 = interp1d(points_coords['x_5'], points_coords['y_5'], kind='linear', fill_value='extrapolate')
    interp_3 = interp1d(points_coords['x_3'], points_coords['y_3'], kind='linear', fill_value='extrapolate')
    interp_line = interp1d(points_coords['x_line'], points_coords['y_line'], kind='linear', fill_value='extrapolate')

    plt.plot(x_vals, interp_17(x_vals), label='17-point model')
    plt.plot(x_vals, interp_9(x_vals), label='9-point model')
    plt.plot(x_vals, interp_5(x_vals), label='5-point model')
    plt.plot(x_vals, interp_3(x_vals), label='3-point model')
    plt.plot(x_vals, interp_line(x_vals), label='Linear dependency', linestyle='--')

    plt.axhline(50, color='orange', linestyle='-', linewidth=2, label='Y=50 (highlighted)')
    plt.scatter(points_coords['x_17'], points_coords['y_17'], color='blue', label='17-point nodes')
    plt.scatter(points_coords['x_9'], points_coords['y_9'], color='red', label='9-point nodes')

    plt.xlabel('Source data')
    plt.ylabel('Converted data (AMIS)')
    plt.title('Comparison of AMIS Conversion Models')
    plt.legend()
    plt.grid(True)
    plt.tight_layout()
    plt.show()

def main():
    df, input_file = read_data_from_file()
    if df is None:
        print("File not selected")
        return

    names = df.iloc[:, 0]
    data = df.iloc[:, 1]

    print(f"Original size: {len(data)}")
    mask = data.notna()
    names = names[mask]
    data_filtered = data[mask].astype(float)
    print(f"Size after removing empty values: {len(data_filtered)}")

    min_val, max_val = select_normalization_bounds()
    if min_val is None or max_val is None:
        min_val = np.min(data_filtered)
        max_val = np.max(data_filtered)

    normalized = normalize_data(data_filtered, min_val, max_val)
    converted, points_coords = amis_conversion(data_filtered, fixed_min=min_val, fixed_max=max_val)

    base, ext = os.path.splitext(input_file)

    df_all = pd.DataFrame({
        df.columns[0]: names,
        str(df.columns[1]) + '_Original': data_filtered,
        'Linear_Normalized': normalized,
        'AMIS_3_points': converted['3_points'],
        'AMIS_5_points': converted['5_points'],
        'AMIS_9_points': converted['9_points'],
        'AMIS_17_points': converted['17_points']
    })
    all_values_file = f"{base}_all_values.xlsx"
    df_all.to_excel(all_values_file, index=False)
    print(f"All values (original and converted) saved: {all_values_file}")

    plot_conversion_models(data_filtered, points_coords)

if __name__ == "__main__":
    main()